/*
 * Copyright (C) 2011-2015 ShenZhen iBOXPAY Information Technology Co.,Ltd.
 * 
 * All right reserved.
 * 
 * This software is the confidential and proprietary
 * information of iBoxPay Company of China. 
 * ("Confidential Information"). You shall not disclose
 * such Confidential Information and shall use it only
 * in accordance with the terms of the contract agreement 
 * you entered into with iBoxpay inc.
 *
 */

package com.iboxpay.sdk;

import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.KeySpec;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.DESedeKeySpec;

//import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;

/**
 * The class Coder.
 *
 * Description: 
 *
 * @author: nieminjie
 * @since: 2016年2月29日	
 * @version: $Revision$ $Date$ $LastChangedBy$
 *
 */

public class Coder {

	// Des密钥算法
	public final static String DES_KEY_ALGORITHM = "DES";

	// 3Des密钥算法
	public final static String DES3_KEY_ALGORITHM = "DESede";

	/**
	 * 加密操作
	 * @param data
	 * 					原文字节数组
	 * 
	 * @param key
	 * 					密钥字节数组
	 * 
	 * @param algorithm
	 * 					算法类型
	 * 
	 * @param mode
	 * 					分组模式
	 * 
	 * @param padding
	 * 					填充模式
	 * 
	 * @return
	 */
	public static byte[] encrypt(byte[] data, byte[] key, String algorithm, String mode, String padding) {
		return encrypt(data, key, algorithm, mode, padding, null);
	}

	/**
	 * 加密操作
	 * @param data
	 * 					原文字节数组
	 * 
	 * @param key
	 * 					密钥字节数组
	 * 
	 * @param algorithm
	 * 					算法类型
	 * 
	 * @param mode
	 * 					分组模式
	 * 
	 * @param padding
	 * 					填充模式
	 * 
	 * @param spec
	 * 					算法参数说明
	 * 
	 * @return
	 */
	public static byte[] encrypt(byte[] data, byte[] key, String algorithm, String mode, String padding, AlgorithmParameterSpec spec) {
		return doCrypt(data, key, Cipher.ENCRYPT_MODE, algorithm, mode, padding, spec);
	}

	/**
	 * 解密操作
	 * @param data
	 * 					密文字节数组
	 * 
	 * @param key
	 * 					密钥字节数组
	 * 
	 * @param algorithm
	 * 					算法类型
	 * 
	 * @param mode
	 * 					分组模式
	 * 
	 * @param padding
	 * 					填充模式
	 * 
	 * @return
	 */
	public static byte[] decrypt(byte[] data, byte[] key, String algorithm, String mode, String padding) {
		return decrypt(data, key, algorithm, mode, padding, null);
	}

	/**
	 * 解密操作
	 * @param data
	 * 					密文字节数组
	 * 
	 * @param key
	 * 					密钥字节数组
	 * 
	 * @param algorithm
	 * 					算法类型
	 * 
	 * @param mode
	 * 					分组模式
	 * 
	 * @param padding
	 * 					填充模式
	 * 
	 * 	@param spec
	 * 					算法参数说明
	 * 
	 * @return
	 */
	public static byte[] decrypt(byte[] data, byte[] key, String algorithm, String mode, String padding, AlgorithmParameterSpec spec) {
		return doCrypt(data, key, Cipher.DECRYPT_MODE, algorithm, mode, padding, spec);
	}

	/**
	 * 加解密操作
	 * @param data
	 * 					操作字节数组
	 * 
	 * @param key
	 * 					密钥字节数组
	 * 
	 * @param opType
	 * 					加解密类型
	 * 
	 * @param algorithm
	 * 					算法类型
	 * 
	 * @param mode
	 * 					分组模式
	 * 
	 * @param padding
	 * 					填充模式
	 * 
	 * @return
	 */
	public static byte[] doCrypt(byte[] data, byte[] key, int opType, String algorithm, String mode, String padding, AlgorithmParameterSpec spec) {

		// 加解密类型
		String crypt = "";
		if (Cipher.ENCRYPT_MODE == opType) {
			crypt = "encrypt";
		} else if (Cipher.DECRYPT_MODE == opType) {
			crypt = "decrypt";
		} else {
			return null;
		}
		try {
			//实例化密钥材料
			KeySpec dks = getKeySpec(key, algorithm);
			//实例化秘密密钥工厂
			SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
			//还原密钥
			Key k = keyFactory.generateSecret(dks);

			Cipher cipher = Cipher.getInstance(Coder.getAlgorithm(algorithm, mode, padding));
			if (null != spec) {
				cipher.init(opType, k, spec);
			} else {
				cipher.init(opType, k);
			}

			// 不填充
			if (Padding.NoPadding.equals(padding)) {

				// 加密
				if (Cipher.ENCRYPT_MODE == opType) {

					// 填充数据，满足8字节倍数
					return cipher.doFinal(fillGap(data));
				} else if (Cipher.DECRYPT_MODE == opType) {

					// 解密，去除手动填充的数据
					return removeGap(cipher.doFinal(data));
				}
			}

			//执行操作
			return cipher.doFinal(data);
		} catch (Exception e) {
			throw new RuntimeException("Error when " + crypt + " with chiperAlgorithm, info: " + e.getMessage(), e);
		}
	}

	/**
	 * 得到算法长度要求的key
	 * @param key
	 * @return
	 */
	public static KeySpec getKeySpec(byte[] key, String algorithm) {
		KeySpec keySpec = null;

		try {
			// Des
			if (DES_KEY_ALGORITHM.equals(algorithm)) {
				keySpec = new DESKeySpec(getKey(key, algorithm));
			} else if (DES3_KEY_ALGORITHM.equals(algorithm)) {
				keySpec = new DESedeKeySpec(getKey(key, algorithm));
			}
		} catch (InvalidKeyException e) {
			throw new RuntimeException("Error when getKeySpec, info: " + e.getMessage(), e);
		}
		return keySpec;
	}

	/**
	 * 得到算法长度要求的key
	 * @param key
	 * @return
	 */
	public static byte[] getKey(byte[] key, String algorithm) {
		int min = 0;

		// Des密钥最少8位
		if (DES_KEY_ALGORITHM.equals(algorithm)) {
			min = 8;
		} else if (DES3_KEY_ALGORITHM.equals(algorithm)) {

			// 3Des密钥最少24位
			min = 24;
		}

		if (key.length >= min) {
			return key;
		}
		byte[] des3Key = new byte[min];

		int i = 0;
		for (; (i + 1) * key.length <= min; i++) {
			System.arraycopy(key, 0, des3Key, key.length * i, key.length);
		}

		if (min % key.length != 0) {

			// 剩余的元素使用key数组前几个字节补上
			System.arraycopy(key, 0, des3Key, key.length * i, min - key.length * i);
		}
		return des3Key;
	}

	/** 
	* 生成Des密钥 
	*  
	* @param seed 
	* 					随即数
	* 
	* @return 
	* @throws Exception 
	*/
	public static String initDesKey(String seed) {
		return initKey(DES_KEY_ALGORITHM, seed);
	}

	/** 
	 * 生成3Des密钥 
	 *  
	 * @param seed 
	 * 					随即数
	 * 
	 * @return 
	 * @throws Exception 
	 */
	public static String init3DesKey(String seed) {
		return initKey(DES3_KEY_ALGORITHM, seed);
	}

	/** 
	* 生成密钥 
	*  
	* @param algorithm
	* 					算法类型
	* 
	* @param seed 
	* 					随即数
	* 
	* @return 
	* @throws Exception 
	*/
	public static String initKey(String algorithm, String seed) {
		SecureRandom secureRandom = null;

		if (seed != null) {
			secureRandom = new SecureRandom();//Base64.decodeBase64(seed));
		} else {
			secureRandom = new SecureRandom();
		}

		KeyGenerator kg = null;
		try {
			kg = KeyGenerator.getInstance(algorithm);
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException("Error when get key generator, ermsg: " + e.getMessage(), e);
		}
		kg.init(secureRandom);

		SecretKey secretKey = kg.generateKey();

		return "";// Base64.encodeBase64String(secretKey.getEncoded());
	}

	/**
	 * 补位操作
	 * @param srcBytes
	 * @return
	 */
	public static byte[] fillGap(byte[] srcBytes) {
		if (null == srcBytes) {
			return null;
		}
		try {
			int len = srcBytes.length;
			int total = (len / 8 + 1) * 8;

			byte[] destBytes = new byte[total];
			System.arraycopy(srcBytes, 0, destBytes, 0, len);
			if (total - len == 1) {
				destBytes[len] = (byte) 0x80;
			} else {
				for (int i = len; i < total; i++) {
					if (i == len) {
						destBytes[i] = (byte) 0x80;
					} else {
						destBytes[i] = (byte) 0x00;
					}

				}
			}
			return destBytes;
		} catch (Exception e) {
			throw new RuntimeException(" Error when msg fill gap " + e.getMessage(), e);
		}
	}

	/**
	 * 移位操作
	 * @param srcBytes
	 * @return
	 */
	public static byte[] removeGap(byte[] srcBytes) {
		if (null == srcBytes) {
			return null;
		}
		byte[] desBytes;
		try {
			int len = srcBytes.length;
			int pos = 0;
			for (int i = len - 1; i >= len - 9; i--) {
				if (srcBytes[i] == -128) {
					pos = i;
					break;
				}
			}
			if (pos == 0) {
				desBytes = srcBytes;
			} else {
				desBytes = new byte[pos];
				System.arraycopy(srcBytes, 0, desBytes, 0, pos);
			}
			return desBytes;
		} catch (Exception e) {
			throw new RuntimeException(" Error when data remove gap " + e.getMessage(), e);
		}

	}

	/**
	 * 获取组合算法
	 * @param chiperType
	 * 					加密类型
	 * 
	 * @param mode
	 * 					分组模式
	 * 
	 * @param padding
	 * 					填充模式
	 * 
	 * @return
	 */
	public static String getAlgorithm(String chiperType, String mode, String padding) {
		if (StringUtils.isBlank(chiperType)) {
			return "";
		}
		StringBuilder sb = new StringBuilder(chiperType);

		// 分组模式
		if (StringUtils.isBlank(mode)) {

			// 默认ECB
			mode = Mode.ECB;
		}
		sb.append("/").append(mode);

		// 填充类型
		if (StringUtils.isBlank(padding)) {

			// 默认NoPadding
			padding = Padding.NoPadding;
		}
		sb.append("/").append(padding);
		return sb.toString();
	}

	/**
	 * 
	 * The class Mode.
	 *
	 * Description: 分组模式
	 *
	 * @author: nieminjie
	 * @since: 2015年7月20日	
	 *
	 */
	public static final class Mode {

		public final static String ECB = "ECB";

		public final static String CBC = "CBC";

		public final static String CTS = "CTS";

		public final static String CTR = "CTR";

		public final static String CFB = "CFB";

		public final static String OFB = "OFB";

		public final static String PCBC = "PCBC";

	}

	/**
	 * 
	 * The class Padding.
	 *
	 * Description: 填充模式
	 *
	 * @author: nieminjie
	 * @since: 2015年7月20日	
	 *
	 */
	public static final class Padding {

		public final static String NoPadding = "NoPadding";

		public final static String ISO10126Padding = "ISO10126Padding";

		public final static String PKCS5Padding = "PKCS5Padding";
	}
}
